use std::mem;

use redb::{ReadableTable, TableError};
use ruma::{
    api::client::filter::FilterDefinition,
    encryption::{CrossSigningKey, DeviceKeys, OneTimeKey},
    serde::Raw,
    DeviceId, DeviceKeyId, OwnedDeviceId, OwnedUserId, UserId,
};
use tracing::info;

use crate::{
    utils::{self, error::Error},
    Result, DATABASE, SALT_LENGTH,
};

const PASSWORDS_DEFINITION: redb::TableDefinition<&str, (String, String)> =
    redb::TableDefinition::new("passwords");

const DEVICE_IDS_DEFINITION: redb::TableDefinition<&str, Vec<String>> =
    redb::TableDefinition::new("device_ids");

const ACCESSTOKEN_DEVICEID_DEFINITION: redb::TableDefinition<&str, (String, String)> =
    redb::TableDefinition::new("access_token_device_id");

const DEVICEID_DEVICEKEYS_DEFINITION: redb::TableDefinition<&str, Vec<u8>> =
    redb::TableDefinition::new("device_id_device_keys");

const ONETIMEKEYID_ONETIMEKEY_DEFINITION: redb::TableDefinition<&str, Vec<u8>> =
    redb::TableDefinition::new("onetimekeyid_onetimekey");

const USERID_MASTERKEYID_DEFINITION: redb::TableDefinition<&[u8], &[u8]> =
    redb::TableDefinition::new("userid_masterkeyid");

const USERID_SELFSIGNINGKEYID_DEFINITION: redb::TableDefinition<&[u8], &[u8]> =
    redb::TableDefinition::new("userid_selfsigningkeyid");

const KEYID_KEY_DEFINITION: redb::TableDefinition<&[u8], &[u8]> =
    redb::TableDefinition::new("keyid_key");

const USER_FILTER_ID_DEFINITION: redb::TableDefinition<&[u8], &[u8]> =
    redb::TableDefinition::new("user_filter_id");

pub struct Service {}

impl Service {
    pub fn new() -> Self {
        let db = &DATABASE;
        let write_ctx = db.begin_write().unwrap();
        write_ctx
            .open_table(ONETIMEKEYID_ONETIMEKEY_DEFINITION)
            .unwrap();
        write_ctx
            .open_table(DEVICEID_DEVICEKEYS_DEFINITION)
            .unwrap();
        write_ctx.open_table(USER_FILTER_ID_DEFINITION).unwrap();
        write_ctx.open_table(USERID_MASTERKEYID_DEFINITION).unwrap();
        write_ctx
            .open_table(USERID_SELFSIGNINGKEYID_DEFINITION)
            .unwrap();
        write_ctx.open_table(KEYID_KEY_DEFINITION).unwrap();

        write_ctx.commit().unwrap();
        Self {}
    }

    pub fn get_filter(
        &self,
        user_id: &UserId,
        filter_id: &str,
    ) -> Result<Option<FilterDefinition>> {
        let mut key = user_id.as_bytes().to_vec();
        key.push(0xFF);
        key.extend_from_slice(filter_id.as_bytes());

        let db = &DATABASE;
        let read_ctx = db.begin_read().unwrap();
        let userfilterid_filter = read_ctx.open_table(USER_FILTER_ID_DEFINITION).unwrap();
        let raw = userfilterid_filter
            .get(key.as_slice())
            .map_err(redb::Error::from)?;

        if let Some(raw) = raw {
            serde_json::from_slice(&raw.value()).map_err(|e| {
                tracing::error!("Invalid filter event in db. {:?}", e);
                Error::bad_database("Invalid filter event in db.")
            })
        } else {
            Ok(None)
        }
    }

    /// Creates a new sync filter. Returns the filter id.
    pub fn create_filter(&self, user_id: &UserId, filter: &FilterDefinition) -> Result<String> {
        let filter_id = utils::random_string(4);

        let mut key = user_id.as_bytes().to_vec();
        key.push(0xFF);
        key.extend_from_slice(filter_id.as_bytes());

        let db = &DATABASE;
        let write_ctx = db.begin_write().unwrap();
        let mut userfilterid_filter = write_ctx.open_table(USER_FILTER_ID_DEFINITION).unwrap();
        userfilterid_filter
            .insert(
                key.as_slice(),
                &serde_json::to_vec(filter)
                    .expect("filter is valid json")
                    .as_slice(),
            )
            .map_err(redb::Error::from)?;

        Ok(filter_id)
    }

    pub fn get_key(
        &self,
        key: &[u8],
        sender_user: Option<&UserId>,
        user_id: &UserId,
        allowed_signatures: &dyn Fn(&UserId) -> bool,
    ) -> Result<Option<Raw<CrossSigningKey>>> {
        let db = &DATABASE;
        let read_ctx = db.begin_read().unwrap();
        self.get_key_inner(&read_ctx, key, sender_user, user_id, allowed_signatures)
    }

    fn get_key_inner(
        &self,
        read_ctx: &redb::ReadTransaction,
        key: &[u8],
        sender_user: Option<&UserId>,
        user_id: &UserId,
        allowed_signatures: &dyn Fn(&UserId) -> bool,
    ) -> Result<Option<Raw<CrossSigningKey>>> {
        info!("Getting key {:?}", key);
        let key_table = read_ctx.open_table(KEYID_KEY_DEFINITION).unwrap();
        key_table.get(key).map_or(Ok(None), |bytes| {
            let mut cross_signing_key =
                serde_json::from_slice::<serde_json::Value>(bytes.unwrap().value())
                    .map_err(|_| Error::BadDatabase("Invalid cross-signing key"))?;
            clean_signatures(
                &mut cross_signing_key,
                sender_user,
                user_id,
                allowed_signatures,
            )?;
            Ok(Some(Raw::from_json(
                serde_json::value::to_raw_value(&cross_signing_key)
                    .expect("Value to RawValue serialization"),
            )))
        })
    }

    pub fn get_user_signing_key(&self, user_id: &UserId) -> Result<Option<Raw<CrossSigningKey>>> {
        let db = &DATABASE;
        let read_ctx = db.begin_read().unwrap();
        let masterkey_table = read_ctx.open_table(USERID_MASTERKEYID_DEFINITION).unwrap();
        let res = masterkey_table
            .get(user_id.as_bytes())
            .map_err(redb::Error::from)?;
        let key_table = read_ctx.open_table(KEYID_KEY_DEFINITION).unwrap();
        if let Some(key) = res {
            let res = key_table.get(&key.value()).map_err(redb::Error::from)?;
            let res: Option<Raw<CrossSigningKey>> = res.map(|x| {
                serde_json::from_slice(x.value())
                    .map_err(|_| Error::bad_database("Invalid cross-signing key"))
                    .unwrap()
            });
            Ok(res)
        } else {
            Ok(None)
        }
    }

    pub fn get_self_signing_key(
        &self,
        sender_user: Option<&UserId>,
        user_id: &UserId,
        allowed_signatures: &dyn Fn(&UserId) -> bool,
    ) -> Result<Option<Raw<CrossSigningKey>>> {
        let db = &DATABASE;
        let read_ctx = db.begin_read().unwrap();
        let key_table = read_ctx
            .open_table(USERID_SELFSIGNINGKEYID_DEFINITION)
            .map_err(redb::Error::from)?;
        let res = key_table
            .get(user_id.as_bytes())
            .map_err(redb::Error::from)?;
        if let Some(key) = res {
            let res = self.get_key_inner(
                &read_ctx,
                &key.value(),
                sender_user,
                user_id,
                allowed_signatures,
            )?;
            Ok(res)
        } else {
            Ok(None)
        }
    }

    pub fn get_master_key(
        &self,
        sender_user: Option<&UserId>,
        user_id: &UserId,
        allowed_signatures: &dyn Fn(&UserId) -> bool,
    ) -> Result<Option<Raw<CrossSigningKey>>> {
        info!("Getting master key for {:?}", user_id);
        let db = &DATABASE;
        info!("Got db lock");
        let read_ctx = db.begin_read().unwrap();
        let key_table = read_ctx.open_table(USERID_MASTERKEYID_DEFINITION).unwrap();
        info!("Got key table");
        let res = key_table
            .get(user_id.as_bytes())
            .map_err(redb::Error::from)?;
        if let Some(key) = res {
            let res = self.get_key_inner(
                &read_ctx,
                &key.value(),
                sender_user,
                user_id,
                allowed_signatures,
            )?;
            Ok(res)
        } else {
            Ok(None)
        }
    }

    pub fn add_device_keys(
        &self,
        user_id: &UserId,
        device_id: &DeviceId,
        device_keys: &Raw<DeviceKeys>,
    ) -> Result<()> {
        let db = &DATABASE;
        let mut key = user_id.to_string();
        key.push('_');
        key.push_str(device_id.as_str());
        let write_ctx = db.begin_write().unwrap();
        {
            let mut device_keys_table = write_ctx
                .open_table(DEVICEID_DEVICEKEYS_DEFINITION)
                .unwrap();
            device_keys_table
                .insert(key.as_str(), serde_json::to_vec(device_keys).unwrap())
                .map_err(redb::Error::from)?;
        }
        write_ctx.commit().map_err(redb::Error::from)?;
        Ok(())
    }
    pub fn get_device_keys(
        &self,
        user_id: &UserId,
        device_id: &DeviceId,
    ) -> Result<Option<Raw<DeviceKeys>>> {
        let db = &DATABASE;
        let mut key = user_id.to_string();
        key.push('_');
        key.push_str(device_id.as_str());
        let read_ctx = db.begin_read().unwrap();
        let device_keys_table = read_ctx.open_table(DEVICEID_DEVICEKEYS_DEFINITION).unwrap();
        let res: Option<redb::AccessGuard<'_, Vec<u8>>> = device_keys_table
            .get(key.as_str())
            .map_err(redb::Error::from)?;
        Ok(res.map(|res| serde_json::from_slice(&res.value()).unwrap()))
    }
    // TODO : Implement this
    pub fn add_onetime_key(
        &self,
        user_id: &UserId,
        device_id: &DeviceId,
        one_time_key_key: &DeviceKeyId,
        one_time_key_value: &Raw<OneTimeKey>,
    ) -> Result<()> {
        let db = &DATABASE;
        let mut key = user_id.to_string();
        key.push('_');
        key.push_str(device_id.as_str());
        key.push('_');
        key.push_str(one_time_key_key.as_str());
        let write_ctx = db.begin_write().unwrap();
        {
            let mut onetimekey_table = write_ctx
                .open_table(ONETIMEKEYID_ONETIMEKEY_DEFINITION)
                .unwrap();
            onetimekey_table
                .insert(
                    key.as_str(),
                    serde_json::to_vec(one_time_key_value).unwrap(),
                )
                .map_err(redb::Error::from)?;
        }
        write_ctx.commit().map_err(redb::Error::from)?;
        Ok(())
    }

    pub fn create_user(&self, user: &OwnedUserId, password: Option<&str>) -> Result<(), Error> {
        let db = &DATABASE;
        let write_ctx = db.begin_write().unwrap();
        let password = password.unwrap_or_default();
        let salt = utils::random_string(SALT_LENGTH);
        let password_hashed = argon2::hash_encoded(
            password.as_bytes(),
            salt.as_bytes(),
            &argon2::Config::original(),
        )
        .unwrap();
        {
            let mut passwords_table = write_ctx.open_table(PASSWORDS_DEFINITION).unwrap();

            passwords_table
                .insert(user.as_str(), &(password_hashed, salt))
                .unwrap();
            // let mut device_ids_table = write_ctx.open_table(DEVICE_IDS_DEFINITION).unwrap();
            // for device_id in user.device_id.iter() {
            //     device_ids_table
            //         .insert(user.user_id.as_str(), device_id.to_string())
            //         .unwrap();
            // }
        }
        write_ctx.commit().unwrap();
        info!("Created user: {:?} with password: {:?}", user, password);
        Ok(())
    }

    pub fn exists(&self, user_id: &UserId) -> Result<bool, Error> {
        let db = &DATABASE;
        let read_ctx = db.begin_read().map_err(redb::Error::from)?;
        let passwords_table = match read_ctx.open_table(PASSWORDS_DEFINITION) {
            Err(TableError::TableDoesNotExist(_)) => {
                return Ok(false);
            }
            res => res,
        }
        .map_err(redb::Error::from)?;
        Ok(passwords_table
            .get(user_id.as_str())
            .map_err(redb::Error::from)?
            .is_some())
    }

    pub fn find_from_token(
        &self,
        access_token: &str,
    ) -> Result<Option<(OwnedUserId, OwnedDeviceId)>, Error> {
        let db = &DATABASE;
        let read_ctx = db.begin_read().unwrap();
        let access_token_device_id_table = read_ctx
            .open_table(ACCESSTOKEN_DEVICEID_DEFINITION)
            .map_err(redb::Error::from)?;
        let result = access_token_device_id_table
            .get(access_token)
            .map_err(redb::Error::from)?;
        Ok(result.map(|res| {
            (
                UserId::parse(res.value().0).unwrap(),
                OwnedDeviceId::from(res.value().1),
            )
        }))
    }

    pub fn password_hash(&self, user_id: &UserId) -> Result<Option<(String, String)>, Error> {
        let db = &DATABASE;
        let read_ctx = db.begin_read().unwrap();
        let passwords_table = read_ctx.open_table(PASSWORDS_DEFINITION).unwrap();
        Ok(passwords_table
            .get(user_id.as_str())
            .map_err(redb::Error::from)?
            .map(|res| res.value()))
    }

    pub fn all_device_ids(&self, user_id: &UserId) -> Result<Vec<OwnedDeviceId>, Error> {
        let db = &DATABASE;
        let read_ctx = db.begin_read().unwrap();
        let device_ids_table = read_ctx.open_table(DEVICE_IDS_DEFINITION).unwrap();
        let device_ids = device_ids_table
            .get(user_id.as_str())
            .map_err(redb::Error::from)?
            .ok_or(Error::BadDatabase("No device ids for user"))?;
        Ok(device_ids
            .value()
            .iter()
            .map(|device_id| OwnedDeviceId::from(device_id.as_str()))
            .collect())
    }

    pub fn set_token(
        &self,
        access_token: &str,
        user_id: &UserId,
        device_id: &DeviceId,
    ) -> Result<(), Error> {
        let db = &DATABASE;
        let write_ctx = db.begin_write().unwrap();
        {
            let mut access_token_device_id_table = write_ctx
                .open_table(ACCESSTOKEN_DEVICEID_DEFINITION)
                .map_err(redb::Error::from)?;
            access_token_device_id_table
                .insert(access_token, (user_id.to_string(), device_id.to_string()))
                .map_err(redb::Error::from)?;
            info!("Setting token: {:?}", access_token);
        }
        write_ctx.commit().map_err(redb::Error::from)?;
        Ok(())
    }

    pub fn create_device(
        &self,
        user_id: &UserId,
        device_id: &DeviceId,
        token: &str,
        _display_name: Option<&str>,
    ) -> Result<(), Error> {
        {
            let db = &DATABASE;
            let write_ctx = db.begin_write().unwrap();
            {
                let mut device_ids_table = write_ctx.open_table(DEVICE_IDS_DEFINITION).unwrap();
                let mut new_device_ids: Vec<String>;
                {
                    let device_ids = device_ids_table
                        .get(user_id.as_str())
                        .map_err(redb::Error::from)?
                        .map(|x| x.value())
                        .unwrap_or(vec![]);
                    new_device_ids = device_ids;
                }
                new_device_ids.push(device_id.to_string());
                device_ids_table
                    .insert(user_id.as_str(), new_device_ids)
                    .map_err(redb::Error::from)?;
            }
            write_ctx.commit().map_err(redb::Error::from)?;
        }
        self.set_token(token, &user_id, &device_id)?;
        Ok(())
    }

    pub(crate) fn parse_master_key(
        &self,
        user: &UserId,
        masterkey: &Raw<CrossSigningKey>,
    ) -> Result<(Vec<u8>, CrossSigningKey)> {
        Err(Error::bad_database("todo"))
        // todo!()
    }

    pub(crate) fn add_cross_signing_keys(
        &self,
        user_id: &UserId,
        master_key: &Raw<CrossSigningKey>,
        self_signing_key: &Option<Raw<CrossSigningKey>>,
        user_signing_key: &Option<Raw<CrossSigningKey>>,
        notify: bool,
    ) -> Result<()> {
        Err(Error::bad_database("todo"))
        // todo!()
    }
}

pub fn clean_signatures<F: Fn(&UserId) -> bool>(
    cross_signing_key: &mut serde_json::Value,
    sender_user: Option<&UserId>,
    user_id: &UserId,
    allowed_signatures: F,
) -> Result<(), Error> {
    if let Some(signatures) = cross_signing_key
        .get_mut("signatures")
        .and_then(|v| v.as_object_mut())
    {
        let new_capacity = signatures.len() / 2;
        for (user, signature) in
            mem::replace(signatures, serde_json::Map::with_capacity(new_capacity))
        {
            let sid = UserId::parse(&user)
                .map_err(|_| Error::BadDatabase("Invalid user ID in signatures"))?;
            if sender_user == Some(user_id) || sid == user_id || allowed_signatures(&sid) {
                signatures.insert(user, signature);
            }
        }
    }

    Ok(())
}
